import * as fs from 'fs';
import { IHeapSnapshot, IHeapNode, LeakTracePathItem } from "@memlab/core";
import { ArkAnalyzer } from "./ArkAnalyzer";
import { Loader } from "../utils/Loader";
import { Finder } from '../utils/Finder';
import { Log, ILoggable } from "../utils/Log";
import { ArkStatCfg } from './ArkStatCfg';
import { ArkNodesMap, ArkTracePathItem } from './ArkTracePath';

/**
 * Ark Heapsnapshot 统计分析器
 */
export class ArkStatAnalyzer extends ArkAnalyzer implements ILoggable {
    public readonly DOMAIN = 'meminsight';
    public readonly TAG = ArkStatAnalyzer.name;
    public static readonly NAME = 'ark-stat-analyzer';
    public static readonly DESC = "Analyze single heapsnapshot file and show statistics.";

    protected snapshot: IHeapSnapshot;
    protected snapshotFilePath: string;

    constructor() {
        super();
        this.type = 'ark-stat-analyzer';
    }

    public override async analyze(args: any[]) : Promise<any> {
        if (args.length < 1) {
            Log.errorX(this, 'Argument count is not enough.');
            return undefined;
        }
        this.snapshotFilePath = args[0] as string;
        if (!fs.existsSync(this.snapshotFilePath)) {
            Log.errorX(this, `File not found, ${this.snapshotFilePath}`);
            return undefined;
        }
        this.snapshot = await Loader.loadFromFile(this.snapshotFilePath);
        return this.snapshot;
    }

    /**
     * get snapshot file path
     * @returns snapshot file path
     */
    public getSnapshotFilePath() : string {
        return this.snapshotFilePath;
    }

    public async getGCRoots() : Promise<Array<IHeapNode>> {
        if (!this.snapshot) {
            Log.errorX(this, 'Heapsnapshot has not been loaded.');
            return [];
        }     
        return Finder.findGCRoots(this.snapshot);
    }

    public async getDetachedNodes() : Promise<Array<IHeapNode>> {
        if (!this.snapshot) {
            Log.errorX(this, 'Heapsnapshot has not been loaded.');
            return [];
        }
        return Finder.findDetachedNodes(this.snapshot);
    }
    
    public async getNodesByFilterCondition(
        filter: (condition: any) => boolean, 
        condition: any) : Promise<Array<IHeapNode>> {
        if (!this.snapshot) {
            Log.errorX(this, 'Heapsnapshot has not been loaded.');
            return [];
        }
        const result = new Array<IHeapNode>();
        this.snapshot.nodes.forEach((node, ) => {
            if (filter(condition)) {
                result.push(node);
            }
        });
        return result;
    }

    public async getNodesByConfig() : Promise<Array<IHeapNode>> {
        if (!this.snapshot) {
            Log.errorX(this, 'Heapsnapshot has not been loaded.');
            return [];
        }
        const result = new Array<IHeapNode>();
        let cfg = this.configuration as ArkStatCfg;
        if (cfg) {
            this.snapshot.nodes.forEach((node, ) => {
                if (!((cfg.Node.enableTypeWhiteList && !cfg.Node.typeWhiteList.includes(node.type))
                    || (cfg.Node.enableTypeBlackList && cfg.Node.typeBlackList.includes(node.type))
                    || (cfg.Node.enableNameWhiteList && !cfg.Node.nameWhiteList.includes(node.name))
                    || (cfg.Node.enableNameBlackList && cfg.Node.nameBlackList.includes(node.name)))) {
                    result.push(node);
                }
            });
        } else {
            Log.errorX(this, 'No analyzer configuration found.');
        }
        return result;
    }

    /**
     * 获取指定节点的最短引用路径
     * @param nodeId node id
     * @returns 最短引用路径
     */
    public async getShortestPathToNode(nodeId: number) : Promise<ArkTracePathItem | undefined> {
        if (!this.snapshot) {
            Log.errorX(this, 'Heapsnapshot has not been loaded.');
            return undefined;
        }
        const node = this.snapshot.nodes.get(nodeId);
        if (!node) {
            Log.errorX(this, `Node(${nodeId}) does not exist.`);
            return undefined;
        }
        return Finder.findShortestPathToGCRoot(this.snapshot, node);
    }

    /**
     * 根据配置参数获取符合条件的节点信息
     * @param nameWhiteList class name 白名单
     * @param typeWhiteList node type 白名单
     * @param enableShortestPath 是否生成最短路径
     * @param enableAggrationPath 是否生成聚合路径，enableShortestPath 为 true 时有效
     * @param maxAggrationLength 最大聚合路径的个数，enableShortestPath 为 true 时有效
     * @param enableMinRetainedSize 是否按最小retained size过滤，enableShortestPath 为 true 时有效
     * @param minRetainedSize 最小retained size，enableShortestPath 为 true 时有效
     * @param descending 聚合路径是否自上而下排序，enableShortestPath 为 true 时有效
     * @returns 节点路径信息，可以按节点名、节点类型、节点到GC Roots的最短路径等信息过滤
     */
    public async getArkNodesMap(
        nameWhiteList: string[],
        typeWhiteList: string[] = ['object'], 
        enableShortestPath: boolean = true,
        enableAggrationPath: boolean = true,
        maxAggrationLength = 3,
        enableMinRetainedSize = false,
        minRetainedSize = 1024 * 1024,
        descending: boolean = true): Promise<ArkNodesMap | undefined> { 
        if (!this.snapshot) {
            Log.errorX(this, 'Heapsnapshot has not been loaded.');
            return undefined;
        }
        const result = new ArkNodesMap();
        this.snapshot.nodes.forEach((node, ) => {
            result.addNode(this.snapshot, node, nameWhiteList, typeWhiteList, enableShortestPath);
        });
        if (enableShortestPath && enableAggrationPath) {
            for (const [key, value] of result) {
                value.getAggrationPathIndexes(
                    descending, 
                    maxAggrationLength, 
                    enableMinRetainedSize ? minRetainedSize : -1);
            }
        }
        return result;
    }

    /**
     * 根据配置获取符合条件的节点信息
     * 配置通过 setConfig(cfg: any) 设置，默认配置参见 ArkStatCfg.defaultArkStatCfg()
     */
    public async getArkNodesMapByConfig() : Promise<ArkNodesMap | undefined> {
        let cfg = this.configuration as ArkStatCfg;
        if (!cfg) {
            Log.errorX(this, 'No analyzer configuration found.');
            return undefined;
        }
        let nameWhiteList = cfg.Node.nameWhiteList;
        let typeWhiteList = cfg.Node.typeWhiteList;
        let enableShortestPath = cfg.Path.enableShortest;
        let enableAggrationPath = cfg.Path.enableAggration;
        let maxAggrationLength = cfg.Path.aggrationLength;
        let enableMinRetainedSize = cfg.Path.enableMinRetainedSize;      
        let minRetainedSize = cfg.Path.minRetainedSize;
        let descending = cfg.Path.aggrationDescending;
        return this.getArkNodesMap(nameWhiteList, typeWhiteList, 
            enableShortestPath, enableAggrationPath, maxAggrationLength,
            enableMinRetainedSize, minRetainedSize, descending);
    }

     /**
     * 获取所有共享对象，Map 以对象tag作为 key，相同tag的对象作为 value
     * @returns Map<string, Array<IHeapNode>>
     */
    public async getSharedHeapNodesMap() : Promise<Map<string, Array<IHeapNode>>> {
        return Finder.getSharedHeapNodesMap(this.snapshot);
    }
}